/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
 * secPaver is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package project

import (
	"fmt"
	pb "gitee.com/openeuler/secpaver/api/proto/project"
	"github.com/pkg/errors"
	"path/filepath"
	"regexp"
	"strings"
)

const maxMacroDepth = 10

var macroRegexp = regexp.MustCompile("\\$\\([a-zA-Z0-9_]+\\)")
var groupRegexp = regexp.MustCompile("^\\$\\([a-zA-Z0-9_]+\\)$")

func ProcessMacroAndGroup(data *pb.ProjectData) error {
	macroList := data.GetResources().GetMacroList()
	groupList := data.GetResources().GetGroupList()
	resList := data.GetResources().GetResourceList()
	appList := data.GetApplicationList().GetApplicationList()

	if len(macroList) == 0 && len(groupList) == 0 {
		return nil
	}

	// replace macro in resourceList
	var err error
	if len(macroList) != 0 && len(resList) != 0 {
		for _, res := range resList {
			res.Path, err = macroReplace(res.GetPath(), macroList)
			if err != nil {
				return errors.Wrapf(err, "fail to replace macro in resourceList: %s", res.GetPath())
			}
		}
	}

	// replace macro in groupList
	if len(macroList) != 0 && len(groupList) != 0 {
		for _, group := range groupList {
			pathList := group.GetResources()
			for i, path := range pathList {
				pathList[i], err = macroReplace(path, macroList)
				if err != nil {
					return errors.Wrapf(err, "fail to replace macro in groupList: %s", path)
				}
			}
		}
	}

	// replace macro and group in spec
	for _, app := range appList {
		attr := app.GetApplication()
		attr.Path, err = MacroReplace(attr.GetPath(), macroList)
		if err != nil {
			return errors.Wrapf(err, "fail to replace macro in application list: %s", attr.GetPath())
		}

		for _, perm := range app.GetPermissionList() {
			perm.Resources, err = groupReplace(perm.GetResources(), groupList)
			if err != nil {
				return errors.Wrapf(err, "fail to replace group in application list")
			}

			rList := perm.GetResources()
			for i, r := range rList {
				rList[i], err = macroReplace(r, macroList)
				if err != nil {
					return errors.Wrapf(err, "fail to replace macro in application list")
				}
			}
		}
	}

	return nil
}

// MacroReplace expand macro of path
func MacroReplace(path string, macros []*pb.Resources_Macro) (string, error) {
	var err error
	for i := 0; i < maxMacroDepth; i++ {
		path, err = macroReplace(path, macros)
		if err != nil {
			return "", err
		}
	}

	if strings.Count(path, "$(") > 0 {
		return "", fmt.Errorf("the depth of macro in %s needs to be less than %d", path, maxMacroDepth)
	}

	return filepath.Clean(path), nil
}

func macroReplace(path string, macros []*pb.Resources_Macro) (string, error) {
	macroStrArr := macroRegexp.FindAllString(path, -1)
	for _, macroStr := range macroStrArr {
		name := strings.TrimSuffix(macroStr, ")")
		name = strings.TrimPrefix(name, "$(")

		value := ""
		for _, macro := range macros {
			if macro.GetName() == name {
				fmt.Println(macro.GetName(), macro.GetValue())
				value = macro.GetValue()
			}
		}

		if value == "" {
			return "", fmt.Errorf("undefined macro %s", name)
		}

		path = strings.Replace(path, macroStr, value, -1)
	}

	return path, nil
}

func groupReplace(paths []string, groups []*pb.Resources_Group) ([]string, error) {
	var rPath []string

	for _, path := range paths {
		if !groupRegexp.MatchString(path) {
			rPath = append(rPath, path)
			continue
		}

		name := strings.TrimSuffix(path, ")")
		name = strings.TrimPrefix(name, "$(")
		find := false

		for _, group := range groups {
			if group.GetName() == name {
				rPath = append(rPath, group.GetResources()...)
				find = true
				break
			}
		}

		if !find {
			rPath = append(rPath, path) // maybe it is macro
		}
	}

	return rPath, nil
}
